/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <sys/stat.h>
#include <libxml/xinclude.h>
#include <glib.h>
#include "paths.h"
#include "xml.h"
struct callback {
	gchar *tag;
	t_xml_callback func;
	gpointer data;
};
static void destfunc(struct callback *c)
{
	g_free(c->tag);
	g_slice_free(struct callback, c);
}

struct t_xml_inst *t_xml_instance_new(void)
{
	struct t_xml_inst *i = g_slice_new(struct t_xml_inst);
	i->ref = 1;
	i->xdg_paths = t_paths_new();
	i->callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)destfunc);
	i->doc = NULL;
	i->root = NULL;
	i->path = NULL;
	return i;
}

void t_xml_instance_unref(struct t_xml_inst *i)
{
	if(i && --i->ref == 0) {
		t_paths_unref(i->xdg_paths);
		g_hash_table_destroy(i->callbacks);
		g_slice_free(struct t_xml_inst, i);
	}
}

void t_xml_register(struct t_xml_inst *i, const gchar *tag, t_xml_callback func, gpointer data)
{
	struct callback *c;
	if(g_hash_table_lookup(i->callbacks, tag)) {
		g_error("Tag '%s' already registered", tag);
		return;
	}
	c = g_slice_new(struct callback);
	c->tag = g_strdup(tag);
	c->func = func;
	c->data = data;
	g_hash_table_insert(i->callbacks, c->tag, c);
}

static gboolean load_file(struct t_xml_inst *i, const gchar *domain, const gchar *filename, const gchar *root_node,
	GSList *paths)
{
	GSList *it;
	gboolean r = FALSE;
	g_assert(i->doc == NULL);
	for(it = paths; !r && it; it = g_slist_next(it)) {
		gchar *path;
		struct stat s;
		if(!domain && !filename)
			path = g_strdup(it->data);
		else
			path = g_build_filename(it->data, domain, filename, NULL);
		if(stat(path, &s) >= 0) {
			i->doc = xmlReadFile(path, NULL, (XML_PARSE_NOBLANKS | XML_PARSE_RECOVER));
			xmlXIncludeProcessFlags(i->doc, (XML_PARSE_NOBLANKS | XML_PARSE_RECOVER));
			if(i->doc) {
				i->root = xmlDocGetRootElement(i->doc);
				if(!i->root) {
					xmlFreeDoc(i->doc);
					i->doc = NULL;
					g_message("%s is an empty XML document", path);
				} else if(xmlStrcmp(i->root->name, (const xmlChar *)root_node)) {
					xmlFreeDoc(i->doc);
					i->doc = NULL;
					i->root = NULL;
					g_message("XML document %s is of wrong type. Root node is not '%s'", path, root_node);
				} else {
					i->path = g_strdup(path);
					r = TRUE;
				}
			}
		}
		g_free(path);
	}
	return r;
}

gboolean t_xml_load_file(struct t_xml_inst *i, const gchar *path, const gchar *root_node)
{
	GSList *paths;
	gboolean r;
	paths = g_slist_append(NULL, g_strdup(path));
	r = load_file(i, NULL, NULL, root_node, paths);
	while(paths) {
		g_free(paths->data);
		paths = g_slist_delete_link(paths, paths);
	}
	return r;
}

gboolean t_xml_load_config_file(struct t_xml_inst *i, const gchar *domain, const gchar *filename,
	const gchar *root_node)
{
	GSList *it, *paths = NULL;
	gboolean r;
	for(it = t_paths_config_dirs(i->xdg_paths); it; it = g_slist_next(it))
		paths = g_slist_append(paths, g_strdup(it->data));
	r = load_file(i, domain, filename, root_node, paths);
	while(paths) {
		g_free(paths->data);
		paths = g_slist_delete_link(paths, paths);
	}
	return r;
}

void t_xml_close(struct t_xml_inst *i)
{
	if(i && i->doc) {
		xmlFreeDoc(i->doc);
		g_free(i->path);
		i->doc = NULL;
		i->root = NULL;
		i->path = NULL;
	}
}

void t_xml_tree(struct t_xml_inst *i, xmlNodePtr node)
{
	g_assert(i->doc);
	while(node) {
		if(node->name) {
			struct callback *c = g_hash_table_lookup(i->callbacks, node->name);
			if(c)
				c->func(node, c->data);
		}
		node = node->next;
	}
}

void t_xml_tree_from_root(struct t_xml_inst *i)
{
	t_xml_tree(i, i->root->children);
}

gchar *t_xml_node_string(xmlNodePtr node)
{
	xmlChar *c = xmlNodeGetContent(node);
	gchar *s;
	if(c)
		g_strstrip((char *)c);
	s = g_strdup(c ? (gchar *)c : "");
	xmlFree(c);
	return s;
}

gint t_xml_node_int(xmlNodePtr node)
{
	xmlChar *c = xmlNodeGetContent(node);
	gint i;
	if(c)
		g_strstrip((char *)c);
	i = c ? atoi((gchar *)c) : 0;
	xmlFree(c);
	return i;
}

gboolean t_xml_node_bool(xmlNodePtr node)
{
	xmlChar *c = xmlNodeGetContent(node);
	gboolean b = FALSE;
	if(c)
		g_strstrip((char *)c);
	if(c && !xmlStrcasecmp(c, (const xmlChar *)"true"))
		b = TRUE;
	else if(c && !xmlStrcasecmp(c, (const xmlChar *)"yes"))
		b = TRUE;
	else if(c && !xmlStrcasecmp(c, (const xmlChar *)"on"))
		b = TRUE;
	xmlFree(c);
	return b;
}

gboolean t_xml_node_contains(xmlNodePtr node, const gchar *val)
{
	xmlChar *c = xmlNodeGetContent(node);
	gboolean r;
	if(c)
		g_strstrip((char *)c);
	r = !xmlStrcasecmp(c, (const xmlChar *)val);
	xmlFree(c);
	return r;
}

xmlNodePtr t_xml_find_node(xmlNodePtr node, const gchar *tag)
{
	while(node) {
		if(!xmlStrcmp(node->name, (const xmlChar *)tag))
			return node;
		node = node->next;
	}
	return NULL;
}

gboolean t_xml_attr_bool(xmlNodePtr node, const gchar *name, gboolean *value)
{
	xmlChar *c = xmlGetProp(node, (const xmlChar *)name);
	gboolean r = FALSE;
	if(c) {
		g_strstrip((char *)c);
		if(!xmlStrcasecmp(c, (const xmlChar *)"true"))
			*value = TRUE, r = TRUE;
		else if(!xmlStrcasecmp(c, (const xmlChar *)"yes"))
			*value = TRUE, r = TRUE;
		else if(!xmlStrcasecmp(c, (const xmlChar *)"on"))
			*value = TRUE, r = TRUE;
		else if(!xmlStrcasecmp(c, (const xmlChar *)"false"))
			*value = FALSE, r = TRUE;
		else if(!xmlStrcasecmp(c, (const xmlChar *)"no"))
			*value = FALSE, r = TRUE;
		else if(!xmlStrcasecmp(c, (const xmlChar *)"off"))
			*value = FALSE, r = TRUE;
	}
	xmlFree(c);
	return r;
}

gboolean t_xml_attr_string(xmlNodePtr node, const gchar *name, gchar **value)
{
	xmlChar *c = xmlGetProp(node, (const xmlChar *)name);
	gboolean r = FALSE;
	if(c) {
		g_strstrip((char *)c);
		*value = g_strdup((gchar *)c);
		r = TRUE;
	}
	xmlFree(c);
	return r;
}

gboolean t_xml_attr_contains(xmlNodePtr node, const gchar *name, const gchar *val)
{
	xmlChar *c = xmlGetProp(node, (const xmlChar *)name);
	gboolean r = FALSE;
	if(c) {
		g_strstrip((char *)c);
		r = !xmlStrcasecmp(c, (const xmlChar *)val);
	}
	xmlFree(c);
	return r;
}
